package httpserver.request;

import httpserver.HTTPException;
import httpserver.general.HTTPHeaders;
import httpserver.general.HTTPStatusCodes;
import httpserver.general.Header;
import httpserver.general.HeaderList;
import httpserver.general.MIMETypes;
import httpserver.general.cookie.Cookie;
import httpserver.general.cookie.CookieParser;
import java.io.*;
import java.net.URLDecoder;

public class Request extends HTTPHeaders {

    private final String DEFAULT_CHARSET = "WINDOWS-1251";
   
    private InputStream in;
    private StartingLine requestLine;
    private HeaderList headerList;
    private String customMessage;

    public Request(InputStream is){
        this.in = is;
    }

    private String readLine() throws IOException{
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        int b;
        boolean crlfFound = false;
        while((b=in.read())>-1){
            if(b=='\r'){
                if((b=in.read())=='\n'){
                    crlfFound = true;
                    break;
                }
                baos.write('\r');
            }
            baos.write(b);
        }
        if(baos.size()==0 && !crlfFound) {
            throw new IOException("no bytes to read!");
        }
       
        return baos.toString(DEFAULT_CHARSET);
    }

    private String readLine(int length) throws IOException {
        int b;
        int bytesRead = 0;
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        while(length!=bytesRead) {
            b=in.read();
            if(b==-1){
                break;
            }
            baos.write(b);
            bytesRead++;
        }
        return baos.toString(DEFAULT_CHARSET);
    }

    private StartingLine parseStartingLine(final String line)
            throws HTTPException {
        int minor, major;
        String version;
        String[] splittedLine = line.split(" ", 3);
        if(splittedLine.length!=3){
            throw new HTTPException(HTTPStatusCodes.HTTP_BAD_REQUEST,
                    "wrong starting line format");
        }
        if(splittedLine[2].charAt(4)=='/'){
            version = splittedLine[2].substring(5, splittedLine[2].length());
        } else {
            throw new HTTPException(HTTPStatusCodes.HTTP_BAD_REQUEST,
                    "wrong version format");
        }
        try{
            minor = Integer.parseInt(version.split("\\.", 2)[0]);
            major = Integer.parseInt(version.split("\\.", 2)[1]);
        } catch (NumberFormatException exc){
            throw new HTTPException(HTTPStatusCodes.HTTP_BAD_REQUEST,
                    "wrong version format");
        }
        return new StartingLine(splittedLine[0], splittedLine[1], major, minor);
    }

    private Header parseHeader(String header) throws HTTPException {
        String name, value;
        int colonIndex;
        colonIndex = header.indexOf(':');
        name = header.substring(0, colonIndex).toLowerCase();
        value = header.substring(colonIndex+1, header.length()).trim();
        return new Header(name, value);
    }

    private String getUEPostEntityBody(int length) throws IOException {
        String result = readLine(length);
        result = result.replace("&", "<br>").replace("=", ": ");
        result = URLDecoder.decode(result, "WINDOWS-1251");
        result = "<html><head><title>" +
                "POST request (application/x-www-form-urlencoded)" +
                "</title></head><body>" +
                result +
                "</body></html>";
        return result;
    }

    private String getMFDPostEntityBody(String boundary)
            throws IOException, HTTPException {
        String result = "<html><head><title>" +
                "POST request (multipart/form-data)" +
                 "</title></head><body>";
        String newLine = readLine();//first boundary;
        String fileName = "";
        boolean file = false;
        ByteArrayOutputStream line, message;
        FileOutputStream fos = null;
        while(true) {
            // check for last boundary
            if(newLine.equals("--"+boundary+"--")){
                break;
            }
            // check for boundary
            if(newLine.equals("--"+boundary)){
                while(!(newLine=readLine()).equals("")){
                    // header
                    String[] t1 = newLine.split(";");
                    String argName, argValue;
                    if(t1.length>0){
                        for(int i=1; i<t1.length; i++){
                            String t2[] = t1[i].split("=");
                            if(t2.length==2){
                                argName = t2[0].trim();
                                argValue = t2[1].trim().replace("\"", "");
                                if(argName.equals("filename")){
                                    result += "\"" + argValue + "\"<br>";
                                    fileName = argValue;
                                    file = true;
                                } 
                                if(argName.equals("name")){
                                    result += argValue + ": ";
                                }
                            }
                        }
                    }
                    System.out.println(newLine + " <<< header");
                }

                // date
                line = new ByteArrayOutputStream();
                message = new ByteArrayOutputStream();
                int b;
                boolean crlfFound = false;
                while(true){
                    while((b=in.read())>-1){
                        if(b=='\r'){
                            if((b=in.read())=='\n'){
                                break;
                            }
                            line.write('\r');
                        }
                        line.write(b);
                    }
                    if(line.size()==0 && !crlfFound) {
                        throw new IOException("no bytes to read!");
                    } else {
                        String possibleBoundary = line.toString().trim();
                        if(possibleBoundary.equals("--"+boundary+"--")
                                || possibleBoundary.equals("--"+boundary)){
                            newLine = possibleBoundary.toString();
                            break;
                        } else {
                            line.writeTo(message);
                            line.reset();
                            line.write('\r');
                            line.write('\n');
                        }
                    }
                }
                // save file or write content
                if(file){
                    fos = new FileOutputStream("www" + File.separator + fileName);
                    fos.write(message.toByteArray());
                    fos.close();
                    file = false;
                } else {
                    result += message.toString(DEFAULT_CHARSET) + "<br>";
                }
            } else {
                throw new HTTPException(HTTPStatusCodes.HTTP_BAD_REQUEST,
                        "boundary mess");
            }
        }
        result += "</body></html>";
        return result;
    }

    public void read() throws IOException, HTTPException {
        // get starting line
        String newLine = readLine();
        requestLine = parseStartingLine(newLine);
        // get headers
        headerList = new HeaderList();
        String completeHeader = "";
        newLine = readLine();
        while(newLine.length()!=0){ 
            completeHeader += newLine; 
            newLine = readLine();
            if(!newLine.startsWith(" ")){ 
                headerList.addHeader(parseHeader(completeHeader));
                completeHeader = "";
            }
        }
        
        if(getMethod().equals("POST") && 
                headerList.hasHeader(HEADER_CONTENT_TYPE)){
            if(headerList.hasHeader(HEADER_CONTENT_LENGTH)){
                int length = Integer.parseInt(headerList.getHeader(HEADER_CONTENT_LENGTH).getValue());
                if(headerList.getHeader(HEADER_CONTENT_TYPE).getValue().trim().equals(
                        MIMETypes.MIME_URL_ENCODED)){
                    customMessage = getUEPostEntityBody(length);
                }
                if(headerList.getHeader(HEADER_CONTENT_TYPE).getValue().startsWith(
                        MIMETypes.MIME_FORM_DATA)){
                    String bdr = headerList.getHeader(HEADER_CONTENT_TYPE).getValue();
                    bdr = bdr.substring(bdr.lastIndexOf(';')+1).trim(); // get boundary substring
                    bdr = bdr.substring(bdr.indexOf('=')+1).trim(); // get boundary
                    customMessage = getMFDPostEntityBody(bdr);
                }
            }
        }
    }

    public String getRequestedURI() {
        return requestLine.getURI();
    }
    public String getMethod(){
        return requestLine.getMethod();
    }
    public String getCustomMessage(){
        return customMessage;
    }
    
    public String getAuthParams(){
        String p = "";
        if(headerList.hasHeader(HEADER_AUTHORIZATION)){
            p = headerList.getHeader(HEADER_AUTHORIZATION).getValue().trim();
            String[] pp = p.split(" ");
            if(pp.length==2){
                p = pp[1].trim();
            }
        }
        return p;
    }
    public boolean hasAuthCookie() throws HTTPException{
        String p;
        if(headerList.hasHeader(HEADER_COOKIE)){
            p = headerList.getHeader(HEADER_COOKIE).getValue().trim();
            CookieParser cp = new CookieParser();
            try{
                Cookie c = cp.parseHeader(p);
                if(c.getName().equals("auth")
                        && c.getValue().equals("committed")){
                    return true;
                }
            } catch(Exception e){
                throw new HTTPException(400);
            }
        }
        return false;
    }
}
